/*                                                     21.Feb.94 - V.1.00
  =======================================================================
  pshar.c
  ~~~~~~~

   Portable version of the shell archive generator program avaliable 
   for Unix systems. Pshar puts readable text files together in a 
   single ascii file, for example to be e-mailed. Pshar stands
   for "Portable SHell ARchive". To pack a list of files (say
   f1.txt f2.asc f3.ps f4.dat), do:
    shar -f pack.sh f1.txt f2.asc f3.ps f4.dat
   or, except for VMS, 
    shar f1.txt f2.asc f3.ps f4.dat > pack.sh
 
   To unpack, on the target system,
    shar -u pack.sh
   or, if the target is a unix machine,
    sh pack.sh.


  Usage:
  ~~~~~~
  pshar [-u shar] [[-a] [-p prefix] [-d delim] [-bcv] 
        [-f shar] files [> shar]]
  where:  
    -a     all the options (v,c,b,-pXX)
    -b     extract absolute paths into current directory
    -c     check filesizes on extraction
    -d     use this EOF delimiter instead of SHAR_EOF
    -f     shar <archive>
    -p     use this as prefix to each line in archived files
    -u     unshar <archive>
    -v     verbose on extraction, incl. echoing filesizes

    files: list of files to be packed. In Unix, wildcard expansion 
           is performed by the operating system. With Borland products,
           one may use wildargs.obj to allow wildcar expansion by the 
           program. Wildcard expansion is not implemented for VMS.

    NOTE: redirection ([> shar] above) does NOT work for VMS!


  Compilation Instructions:
  ~~~~~~~~~~~~~~~~~~~~~~~~~

   SunOS/Solaris 1.0:
    Use cc, acc, or gcc:
     cc -o shar shar.c
    May use wildcards for file name expansion (OS embedded!)

   VMS:
    $ cc shar.c
    $ link shar
    $ shar    = $"''f$environment(""DEFAULT"")'"shar
    $ unshar  = $"''f$environment(""DEFAULT"")'"shar -u

   MSDOS
    Use tcc or bcc (MS-C should do as well- but not tested!):
     tcc shar.c 
    or (check for the right path of wildargs.obj!)
     tcc shar.c \tc\lib\wildargs.obj
    [ The latter is for being able to use file specs like f?.* in the 
      example above, instead of issuing all the file names!]
 
 
  History:
  ~~~~~~~~

     v 940221 Simao F.Campos Neto (simao@cpqd.ansp.br) Ported 
       to work in SunOS 4.1.2 (cc, acc, gcc) and up, and 
       MSDOS (TurboC).
 
     v 870307 T. Stark (h462batms@gallua.bitnet) for VAX/VMS
       - added a -f (shar) switch because of lack of > in DCL.
 
     v 860716 M. Kersenbrock (tektronix!copper!michaelk) for Z80-CPM
       - enhanced usage message
 
     v 860712 D. Wecker for ULTRIX and the AMIGA
       - stripped down.. does patterns but no directories
       - added a -u (unshar) switch

                             PLEASE NOTE THAT:
  *------------------------- ~~~~~~~~~~~~~~~~~~ ------------------------*
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR      *
  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED      *
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. *
  *---------------------------------------------------------------------*

  =======================================================================
*/
#define PSHAR 100

#if defined(__MSDOS__) && !defined(MSDOS)
#define MSDOS
#endif

#if defined (CPM)
#include "c:stdio.h"
#include "c:fcntl.h"
#else
#include <stdio.h>
#include <stdlib.h>
#if defined (MSDOS)
#  include <string.h>
#else
#  include <strings.h>
#endif
#endif

#if defined (CPM)
#define void int
#define fputc putc
extern char    *getenv(), *malloc(), *index(), *rindex();
int             cpmversion;

#endif

#if defined (AMIGA)
#include <exec/types.h>
extern char    *getenv(), *scdir(), *malloc(), *index();
#endif

#if defined (ULTRIX) || defined (sparc) || defined(MSDOS) || defined(__STDC__)
#include <sys/types.h>
#include <time.h>
extern char    *getenv(), *scdir(), *index();
#if defined(MSDOS)
extern void *malloc();
#else
extern char *malloc();
#endif
#endif

#ifdef unix
#include <unistd.h>
#endif

#if defined (VMS)
#include <types.h>
extern char    *getenv(), *scdir(), *malloc();
#endif

/* Smart function prototypes: for [ag]cc, VaxC, and [tb]cc */
#if defined(__STDC__) || defined(VMS) || defined(MSDOS)
#define ARGS(s) s
#else
#define ARGS(s) ()
#endif

/* Function prototypes - generated by cproto -m -M ARGS */
int header ARGS((char *ppchFiles[]));
int archive ARGS((char *input, char *output));
void shar ARGS((char *file));
int getpat ARGS((char *pattern));
int getopt ARGS((int nargc, char **nargv, char *ostr));
int local_getarg ARGS((int nargc, char **nargv));
int dounshar ARGS((char *ArcNam));
void getshpar ARGS((char *line, char *sea, char *par));

#if defined (CPM) || defined(MSDOS)
void strlower ARGS((char *string));
#endif


/* Psedo-functions and special symbols */
#define BADCH   ((int)'?')
#define EMSG    ""
#define tell(s) {fputs(*nargv,stderr);fputs((s),stderr);fputc(optopt,stderr);\
		 fputc('\n',stderr);return(BADCH);}
#define rescanopts()    (optind = 1)

/* Global variables */
int             optind = 1,	/* index into parent argv vector */
                optopt;		/* character checked for validity */
long            fsize;		/* length of file */
char           *optarg;		/* argument associated with option */
char           *sav[100];	/* saved file names */
int             savind;		/* save index */
FILE           *out;		/* Output file pointer - default to stdout */

/* Global Variables: OPTIONS */
int             Verbose = 0;	/* provide append/extract feedback */
int             Basename = 0;	/* extract into basenames */
int             Count = 0;	/* count characters to check transfer */
char           *Delim = "SHAR_EOF";	/* put after each file */
char            Filter[100] = "cat";	/* used to extract archived files */
char           *Prefix = NULL;	/* line prefix to avoid funny chars */
int             UnShar = 0;	/* do we unshar an input file? */

char            Usage1[] =
"\nPSHAR: Create/extract file archive for extraction by /bin/sh (normally).\n\
\n\
usage:\n\
\n\
  shar [-u shar] [[-a] [-p prefix] [-d delim] [-bcv] [-f shar] files [> shar]]\n\
\n\
        where:  -a  all the options (v,c,b,-pXX)\n";

char            Usage2[] =
"                -b  extract absolute paths into current directory\n\
                -c  check filesizes on extraction\n\
                -d  use this EOF delimiter instead of SHAR_EOF\n";

char            Usage3[] =
"                -f  shar <archive>\n\
		-p  use this as prefix to each line in archived files\n\
                -u  unshar <archive>\n";

char            Usage4[] =
"                -v  verbose on extraction, incl. echoing filesizes\n";


#define SED "sed 's/^%s//'"	/* used to remove prefix from lines */

#if defined (CPM)
#define OPTSTRING "U:AP:D:BCV:F:"
#else
#define OPTSTRING "u:ap:d:bcv:f:"
#endif

#if (defined (VMS) || defined(MSDOS)) && !defined(__GNUC__)
char           *index(s, c)
  char           *s;
  char            c;
{
  while (*s != 0 && *s != c)
    s++;
  if (*s == 0 && *s != c)
    s = 0;
  return (s);
}
#endif

int             header(ppchFiles)
  char           *ppchFiles[];
{
  extern char    *ctime();
  auto long       clock;
  register char **ppchList;
  char           *pchOrg;
  char           *pchName;

#ifndef MSDOS
  extern char *getenv(char *);
  void time(long *tloc);
#endif

  pchOrg = (char *)getenv("ORGANIZATION");
  pchName = (char *)getenv("NAME");

  fputs("#\tThis is a shell archive.\n", out);
  fputs("#\tRemove everything above and including the cut line.\n", out);
  fputs("#\tThen run the rest of the file through sh.\n", out);
  fputs("#----cut here-----cut here-----cut here-----cut here----#\n", out);
  fputs("#!/bin/sh\n", out);
  fputs("# shar:    Shell Archiver\n", out);
  fputs("#\tRun the following text with /bin/sh to create:\n", out);
  for (ppchList = ppchFiles; *ppchList; ++ppchList)
    fprintf(out, "#\t%s\n", *ppchList);
#if defined (CPM)
  if (cpmversion >= 0x30)
  {
#endif
    time(&clock);
    fprintf(out, "# This archive created: %s", ctime(&clock));
#if defined (CPM)
  }
#endif
  if (pchName)
    fprintf(out, "# By:\t%s (%s)\n", pchName,
	    pchOrg ? pchOrg : "Dave Wecker Midnight Hacks");
  return (0);
}

int             archive(input, output)
  char           *input, *output;
{
  auto char       line[BUFSIZ];
  register FILE  *ioptr;

  if ((ioptr = fopen(input, "r"))!=NULL)
  {
    fprintf(out, "%s << \\%s > %s\n", Filter, Delim, output);
    while (fgets(line, BUFSIZ, ioptr))
    {
      if (Prefix)
	fprintf(out, "%s\n", Prefix);
      fputs(line, out);
      if (Count)
	fsize += strlen(line);
    }
    fprintf(out, "%s\n", Delim);
    (void) fclose(ioptr);
    return (0);
  }
  else
  {
    fprintf(stderr, "shar: Can't open '%s'\n", input);
    return (1);
  }
}


void            shar(file)
  char           *file;
{
  register char  *basefile;
/*  extern void exit(int status);*/

  basefile = file;
  if (!strcmp(file, "."))
    return;
  fsize = 0;
  if (Basename)
  {
    while (*basefile)
      basefile++;		/* go to end of name */
    while (basefile > file && *(basefile - 1) != '/')
      basefile--;
  }
  if (Verbose)
    fprintf(out, "echo shar: extracting %s\n", basefile);
  if (archive(file, basefile))
    exit(66);
  if (Count)
  {
    fprintf(out, "if test %ld -ne \"`wc -c %s`\"\n", fsize, basefile);
    fprintf(out, "then\necho shar: error transmitting %s ", basefile);
    fprintf(out, "'(should have been %ld characters)'\nfi\n", fsize);
  }
}

int             main(argc, argv)
  int             argc;
  char          **argv;
{
  auto char      *ppchFiles[256];
  register int    C;
  register char **ppchList = ppchFiles;
  register int    errflg = 0;

  out = stdout;

#if defined (CPM)
  cpmversion = (bdoshl(0x0c, 0) & 0xff);
#endif

  while (EOF != (C = getopt(argc, argv, OPTSTRING)))
  {
#if defined (CPM)
    switch (isupper(C) ? tolower(C) : C)
    {
#else
    switch (C)
    {
#endif
    case 'v':
      Verbose++;
      break;
    case 'c':
      Count++;
      break;
    case 'b':
      Basename++;
      break;
    case 'd':
      Delim = optarg;
      break;
    case 'a':			/* all the options */
      optarg = "XX";
      Verbose++;
      Count++;
      Basename++;
      /* fall through to set prefix */
    case 'p':
      (void) sprintf(Filter, SED, Prefix = optarg);
      break;
    case 'u':
      UnShar++;
      dounshar(optarg);
      break;
    case 'f':
      if (!(out = fopen(optarg, "w")))
      {
	fprintf(stderr, "shar: can't open archive %s\n", optarg);
	exit(0);
      }
      break;
    default:
      errflg++;
    }
  }
  if (UnShar)
    exit(0);

  C = local_getarg(argc, argv);
  if (errflg || EOF == C)
  {
    if (EOF == C)
      fprintf(stderr, "shar: No input files\n");
    fprintf(stderr, "%s%s%s%s", Usage1, Usage2, Usage3, Usage4);
    exit(1);
  }

  savind = 0;
  do
  {
    if (getpat(optarg))
      exit(2);
  }
  while (EOF != (C = local_getarg(argc, argv)));

  sav[savind] = 0;
  header(sav);
  for (ppchList = sav; *ppchList; ++ppchList)
  {

#if defined (CPM) || defined(MSDOS)
    strlower(*ppchList);
#endif

    shar(*ppchList);
  }
  fputs("#\tEnd of shell archive\n", out);
  fputs("exit 0\n", out);
  fclose(out);
#ifdef VMS
  exit(0);
#else
  return(0);
#endif
}

int getpat(pattern)
  char           *pattern;
{
  register char  *ptr;
  extern void * malloc(size_t size);

#if defined (AMIGA)
  while (ptr = scdir(pattern))
  {
#else
  ptr = pattern;
  {
#endif
    sav[savind] = (char *)malloc(strlen(ptr) + 1);
    strcpy(sav[savind++], ptr);
#if defined (CPM)
    temp = open(ptr, O_RDONLY);
    if (temp == -1)
    {
#else
    if (access(ptr, 4))
    {
#endif
      fprintf(stderr, "No read access for file: %s\n", ptr);
      return (-1);
    }

#if defined (CPM)
    close(temp);
#endif
  }
  return (0);
}


/*
 * get option letter from argument vector
 */
int             getopt(nargc, nargv, ostr)
  int             nargc;
  char          **nargv, *ostr;
{
  register char  *oli;		/* option letter list index */
  static char    *place = EMSG;	/* option letter processing */
  if (!*place)
  {				/* update scanning pointer */
    if (optind >= nargc || *(place = nargv[optind]) != '-' || !*++place)
      return (EOF);
    if (*place == '-')
    {				/* found "--" */
      ++optind;
      return EOF;
    }
  }				/* option letter okay? */
  if ((optopt = (int) *place++) == (int) ':' || !(oli = index(ostr, optopt)))
  {
    if (!*place)
      ++optind;
    tell(": illegal option -- ");
  }
  if (*++oli != ':')
  {				/* don't need argument */
    optarg = NULL;
    if (!*place)
      ++optind;
  }
  else
  {				/* need an argument */
    if (*place)
    {				/* no white space */
      optarg = place;
    }
    else
    {
      if (nargc <= ++optind)
      {				/* no arg */
	place = EMSG;
	tell(": option requires an argument -- ");
      }
      else
      {
	optarg = nargv[optind];	/* white space */
      }
    }
    place = EMSG;
    ++optind;
  }
  return (optopt);		/* dump back option letter */
}


int
                local_getarg(nargc, nargv)
  int             nargc;
  char          **nargv;
{
  if (nargc <= optind)
  {
    optarg = (char *) 0;
    return EOF;
  }
  else
  {
    optarg = nargv[optind++];
    return 0;
  }
}

int dounshar(ArcNam)
  char           *ArcNam;
{
  register int    i;
  register FILE  *inptr, *outptr;
  auto char       line[BUFSIZ];
  int             DirNum = -1;
  int             Prefix = 0;
  char            Dirs[5][40], FilNam[128], Delim[40], ScrStr[128];
  char           *ptr;

  if (!(inptr = fopen(ArcNam, "r")))
  {
    fprintf(stderr, "shar: Can't open archive '%s'\n", ArcNam);
    return(1);
  }
  while (fgets(line, BUFSIZ, inptr))
  {
    if (strncmp(line, "sed ", 4) == 0)
    {
      Prefix = 0;
      if (!(ptr = index(line, '/')))
	goto getfil;
      if (*++ptr == '^')
	++ptr;
      while (*ptr++ != '/')
	Prefix++;
      goto getfil;
    }
    else if (strncmp(line, "cat ", 4) == 0)
    {
      Prefix = 0;
      ;
  getfil:

#if defined (VMS)
      strcpy(FilNam, "[");
#else
      FilNam[0] = 0;
#endif

      for (i = 0; i <= DirNum; i++)
      {

#if defined (VMS)
	strcat(FilNam, ".");
	strcat(FilNam, Dirs[i]);
#else
	strcat(FilNam, Dirs[i]);
	strcat(FilNam, "/");
#endif

      }


#if defined (VMS)
      strcat(FilNam, "]");
#endif

      getshpar(line, ">", ScrStr);
      strcat(FilNam, ScrStr);

#if defined (CPM)
      tocpmformat(FilNam);	/* tweek format as needed */
#endif
      getshpar(line, "<<", Delim);
      fprintf(stderr, "Creating %s ...", FilNam);
      outptr = fopen(FilNam, "w");
      while (fgets(line, BUFSIZ, inptr))
      {
	if (strncmp(line, Delim, strlen(Delim)) == 0)
	  break;
	if (outptr)
	  fputs(&line[Prefix], outptr);
      }
      if (outptr)
      {
	fclose(outptr);
	fprintf(stderr, "...done\n");
      }
      else
	fprintf(stderr, "...error in creating file\n");
    }
    else if (strncmp(line, "mkdir ", 6) == 0)
    {
      fprintf(stderr, "Need to make directory: %s\n", &line[6]);
    }
    else if (strncmp(line, "chdir ", 6) == 0)
    {
      if (line[6] == '.' && line[7] == '.')
	DirNum--;
      else
	strcpy(Dirs[++DirNum], &line[6]);
      if (DirNum < -1)
	DirNum = -1;
    }
    else if (strncmp(line, "cd ", 3) == 0)
    {
      if (line[3] == '.' && line[4] == '.')
	DirNum--;
      else
	strcpy(Dirs[++DirNum], &line[3]);
      if (DirNum < -1)
	DirNum = -1;
    }
  }
  fclose(inptr);
  return(0);
}

void getshpar(line, sea, par)
  char           *line, *sea, *par;
{
  register char  *scr1, *scr2;

  while (*line)
  {
    scr1 = line;
    scr2 = sea;
    while (*scr1 && *scr2 && *scr1 == *scr2)
    {
      scr1++;
      scr2++;
    }
    if (*scr2 == 0)
    {
      if (*scr1 == 0)
      {
	*par = 0;
	return;
      }
      while (*scr1 == ' ' || *scr1 == '\t' ||
	     *scr1 == '\\' || *scr1 == '\'' || *scr1 == '"')
	scr1++;
      while (*scr1 != 0 && *scr1 != ' ' && *scr1 != '\t' &&
	     *scr1 != '\\' && *scr1 != '\'' && *scr1 != '"' &&
	     *scr1 != '\n' && *scr1 != '\r')
	*par++ = *scr1++;
      *par = 0;
      return;
    }
    line++;
  }
  *par = 0;
}


#if defined (CPM)

tocpmformat(filename)
  char           *filename;
{
  char            buffer[100];
  char            extension[100];
  register char  *temp;
  int             mod = 0;

  strcpy(buffer, filename);

  /* Make sure we get rid of any pathnames */
  if ((temp = rindex(buffer, '/')) != 0)
  {
    strcpy(buffer, (char *) ((temp - buffer) + filename + 1));
    mod = 1;
  }

  if (strlen(filename) <= 8)
  {
    if (mod != 0)
    {
      strcpy(filename, buffer);
    }
    return (0);
  }

  /* If it already is in "CPM" format we'll check if we need to truncate the
   * front filename part. */
  if ((temp = index(buffer, '.')) != 0)
  {
    if ((temp - buffer) < 8)
    {
      if (mod != 0)
      {
	strcpy(filename, buffer);
      }
      return (0);
    }
    else
    {
      strcpy(extension, temp);
      strcpy(&buffer[8], extension);
      buffer[12] = '\0';
      strcpy(filename, buffer);
      return (1);
    }
  }

  /* OK, filename is longer than can be handled, and it doesnt have a
   * filetype "." marker already.  We will put one in to minimize truncation. */
  strcpy(extension, &buffer[8]);
  buffer[8] = '.';
  strcpy(&buffer[9], extension);
  buffer[12] = '\0';
  strcpy(filename, buffer);
  return (2);
}
#endif

#if defined (CPM) || defined (MSDOS)

void strlower(string)
  char           *string;
{
  register char  *pointer;
  char            c;
  for (pointer = string; (c = *pointer) != '\0'; pointer++)
  {
    if (isupper(c))
      *pointer = tolower(c);
  }
}

#endif
